//
//  FFmpegTest.m
//  FFmpegDemo
//
//  Created by lw on 2018/6/18.
//  Copyright © 2018年 lee. All rights reserved.
//

#import "FFmpegTest.h"

@implementation FFmpegTest

//测试配置信息
+ (void)ffmpegTestConfig {
    const char *configuration = avcodec_configuration();
    NSLog(@"配置信息：%s",configuration);
}

//视频解码
+ (void)ffmpegVideoDecodeOpenFile:(NSString *)filePath outFilePath:(NSString *)outFilePath {
    
    //1.注册组件 编码器 解码器等
    av_register_all();
    
    
    //2.打开封装格式文件 mp4 mov wmv等
    AVFormatContext *avformat_context = avformat_alloc_context();
    const char *url = [filePath UTF8String];
    
    /*参数:
        1封装格式上下文 保存整个视频信息（解码器、编码器等。。）信息：码率、帧率等。。
        2打开视频地址path 3指定输入封装格式 4指定默认配置信息
     */
    int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
    if (avformat_open_input_result != 0) {
        //失败了 获取错误信息
        char *error_info = NULL;
        av_strerror(avformat_open_input_result, error_info, 1024);
        NSLog(@"打开文件失败");
        return;
    }
    NSLog(@"打开文件成功");
    
    
    //3.查找视频流 ->拿到视频信息
    /*参数一：封装格式上下文 参数二：指定默认配置*/
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0) {
        NSLog(@"查找失败%d",avformat_find_stream_info_result);
        return;
    }
    
    //4.查找视频解码器
    int av_stream_index = -1;
    //1).查找视频流索引位置
    for (int i=0; i<avformat_context->nb_streams; i++) {
        //判断流类型：视频流、音频流、字母流等。。。
        if (avformat_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            av_stream_index = i;
            break;
        }
    }
    
    //2).根据视频流索引，获取解码器上下文
    AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
    AVCodecParameters *avcodecParameters = avformat_context->streams[av_stream_index]->codecpar;
    //3).根据解码器上下文，获得解码器id，然后查找解码器
    AVCodec *avcodec = avcodec_find_decoder(avcodecParameters->codec_id);
    
    //5.打开解码器
    int avcodec_open_result = avcodec_open2(avcodec_context, avcodec, NULL);
    if (avcodec_open_result != 0) {
        NSLog(@"打开解码器失败");
        return;
    }
    
    NSLog(@"解码器名称：%s",avcodec->name);
    
    //6.读取视频压缩数据 ->循环读取  每读取一帧数据 立马解码一帧数据
    
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));//分配一帧大小的内存
    AVFrame *av_frame_in = av_frame_alloc();//开辟一块内存空间
    
    
    int decode_result = 0;
    /*
     参数一、二：原始视频像素数据格式宽 、高
     参数三：原始视频像素数据格式类型
     参数四、五：目标视频像素格式宽、高
     参数六：目标视频像素格式类型
     SWS_BICUBIC:字节对齐方式
     */
    struct SwsContext *swsContext = sws_getContext(avcodec_context->width, avcodec_context->height, avcodec_context->pix_fmt, avcodec_context->width, avcodec_context->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    
    //创建一个yuv420视频像素数据格式缓冲区(一帧数据)
    AVFrame *avframe_yuv420p = av_frame_alloc();
    //给缓冲区设置类型->yuv420p类型 得到yuv420p缓冲区大小
    /*
     参数一：视频像素数据格式类型->YUV420P格式
     参数二：一帧视频像素数据宽 = 视频宽
     参数三：一帧视频像素数据高 = 视频高
     参数四：字节对齐方式->默认是1
     */
    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height, 1);
    //开辟一块内存空间
    unsigned char *out_buffer = (unsigned char *)av_malloc(buffer_size);
    /*
     参数一：目标->填充数据(avframe_yuv420p)
     参数二：目标->每一行大小
     参数三：原始数据
     参数四：目标->格式类型
     参数五、六：宽、高
     参数七：字节对齐方式
     */
    av_image_fill_arrays(avframe_yuv420p->data, avframe_yuv420p->linesize, out_buffer, AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height, 1);
    
    int y_size, u_size, v_size;
    
    //5.2 将yuv420数据写入.yuv文件中
    //打开写入文件
    const char *outUrl = [outFilePath UTF8String];
    FILE *file_yuv420p = fopen(outUrl, "wb+");
    if (file_yuv420p == NULL) {
        NSLog(@"输出文件打开失败");
        return;
    }
    
    int current_index = 0;
    
    /*参数一：封装格式上下文 参数二：一帧压缩数据*/
    while (av_read_frame(avformat_context, packet) >= 0) {
        //>=0读取到了，<0读取错误或读取完毕
        //2)是否是视频流
        if (packet->stream_index == av_stream_index) {
            
//7.视频解码 -> 播放视频 -> 得到视频像素数据
            
            //3).解码一帧压缩数据->得到视频像素数据->yuv格式
            //发送一帧视频压缩数据
            avcodec_send_packet(avcodec_context, packet);
            //接收一帧视频压缩数据->进行解码
            decode_result = avcodec_receive_frame(avcodec_context, av_frame_in);
            if (decode_result == 0) {
                //解码成功
                //注意：不能保证解码出来的一帧视频像素数据格式是yuv，还有yuv420P、yuv422P等
                //sws_scale类型转换：将解码出来的视频像素数据格式转成->yuv420P
                /*
                 参数一：视频像素格式上下文
                 参数二：原来的视频像素数据格式->输入数据
                 参数三：原来的视频像素数据格式->输入画面每一行大小
                 参数四：原来的视频像素数据格式->输入画面每一行开始位置(填写：0->表示从原点开始读取)
                 参数五：原来的视频像素数据格式->输入数据行数
                 参数六：转换类型后视频像素数据格式->输出数据
                 参数七：转换类型后视频像素数据格式->输出画面每一行大小
                 */
                sws_scale(swsContext, (const uint8_t *const *)av_frame_in->data, av_frame_in->linesize, 0, avcodec_context->height, avframe_yuv420p->data, avframe_yuv420p->linesize);
                //将yuv420p数据写入.yuv文件中
                //计算yuv大小：y表示 亮度，uv表示 色度
                //yuv420p格式规范一：y结构表示一个像素
                //yuv420p格式规范二：4个像素点对应一个(4y = u = v)
                y_size = avcodec_context->width * avcodec_context->height;
                u_size = y_size / 4;
                v_size = y_size / 4;
                //写入.yuv文件
                fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
                fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
                fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
                
                current_index++;
                NSLog(@"当前解码第%d帧",current_index);
            }
            
        }
    }
    
    //8.释放内存 关闭解码器 -> 解码完成
    av_packet_free(&packet);
    fclose(file_yuv420p);
    av_frame_free(&av_frame_in);
    av_frame_free(&avframe_yuv420p);
    free(out_buffer);
    avcodec_close(avcodec_context);
    avformat_free_context(avformat_context);
    
}

//音频解码
+ (void)ffmpegAudioDecodeOpenFile:(NSString *)filePath outFilePath:(NSString *)outFilePath {
    //1.注册组件
    av_register_all();
    
    //2.打开封装格式->打开文件
    AVFormatContext *avformat_context = avformat_alloc_context();
    const char *url = [filePath UTF8String];
    int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
    if (avformat_open_input_result != 0) {
        NSLog(@"打开文件失败");
        return;
    }
    
    //3.查找音频流->拿到音频信息
    int avformate_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_open_input_result < 0) {
        NSLog(@"查找失败%d",avformate_find_stream_info_result);
        return;
    }
    
    //4.查找音频解码器
    int av_stream_index = -1;
    for (int i= 0; i < avformat_context->nb_streams; i++) {
        //判断是否是音频流
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            av_stream_index = i;
            break;
        }
    }
    
    AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
    AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL) {
        NSLog(@"查找音频解码器失败");
        return;
    }
    
    //5.打开音频解码器
    int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
    if (avcodec_open2_result != 0) {
        NSLog(@"打开音频解码器失败");
        return;
    }
    
    //6.读取音频压缩数据
    //AVPacket 音频压缩数据->aac格式、mp3格式
    //创建音频压缩数据帧
    AVPacket *avpacket = (AVPacket *)av_malloc(sizeof(AVPacket));
    //创建音频采样数据帧
    AVFrame *avframe = av_frame_alloc();
    
    /*参数*/
    // 1.音频采样数据上下文 保存音频信息->目录
    SwrContext *swr_context = swr_alloc();
    // 2.out_ch_layout输出声道布局类型(立体声、环绕声、机器人等声音类型)
//    int out_ch_layout = AV_CH_LAYOUT_STEREO;//立体声
    int64_t out_ch_layout = av_get_default_channel_layout(avcodec_context->channels);
    // 3.out_sample_fmt输出的采样精度 (编码：每个像素点占用的内存：16位pcm、8位pcm等)如：采样精度8位=1字节 采样精度16位=2字节
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//直接指定
//    int out_sample_fmt = avcodec_context->sample_fmt;//动态获取
    // 4.out_sample_rate,输出采样率44100hz
    int out_sample_rate = avcodec_context->sample_rate;
    // 5.in_ch_layout 输入声道布局类型
    int64_t in_ch_layout = av_get_default_channel_layout(avcodec_context->channels);
    // 6.in_sample_fmt 输入采样精度
    enum AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
    // 7.in_sample_rate 输入的采样率
    int in_sample_rate = avcodec_context->sample_rate;
    // 8.log_offset log日志
    int log_offset = 0;//从0开始统计 不需要偏移
    // 9.log_ctx log上下文
    
    //给上下文设置默认参数
    swr_alloc_set_opts(swr_context, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, log_offset, NULL);
    //初始化音频采样数据上下文
    swr_init(swr_context);
    
    //缓冲区大小=采样率（44100）*采样精度（16位 = 2字节）
    int MAX_AUDIO_SIZE = 44100 * 2;
    uint8_t *out_buffer = av_malloc(MAX_AUDIO_SIZE);
    //输出声道数量
    int out_count = av_get_channel_layout_nb_channels(out_ch_layout);
    
    int audio_decode_result = 0;
    
    //打开文件
    const char *coutFilePath = [outFilePath UTF8String];
    FILE *out_file_pcm = fopen(coutFilePath, "wb");
    if (out_file_pcm == NULL) {
        NSLog(@"打开音频输出文件失败");
        return;
    }
    
    int current_count = 0;
    while (av_read_frame(avformat_context, avpacket) >= 0) {
        NSLog(@"读取一帧压缩数据成功");
        if (avpacket->stream_index == av_stream_index) {
            //音频解码
            //1.发送音频压缩数据包
            avcodec_send_packet(avcodec_context, avpacket);
            //2.解码一帧音频压缩数据包->得到音频采样数据
            audio_decode_result = avcodec_receive_frame(avcodec_context, avframe);
            if (audio_decode_result == 0) {
                //音频压缩数据解码成功
                //3.类型转换 输出音频采样数据格式是pcm格式
                /*参数
                 1.音频采样数据上下文
                 2.输出音频采样数据
                 3.输出音频采样数据的大小
                 4.输入音频采样数据
                 5.输入音频采样数据的大小
                 */
                swr_convert(swr_context, &out_buffer, MAX_AUDIO_SIZE, (const uint8_t **)avframe->data, avframe->nb_samples);
                
                //4获取缓冲区实际存储大小
                /*
                 1.行大小
                 2.输出声道的数量
                 3.输入的大小
                 4.输出音频采样数据格式
                 5.字节对齐方式
                 */
                int nb_samples = avframe->nb_samples;
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_count, nb_samples, out_sample_fmt, 1);
                //5写入文件
                fwrite(out_buffer, 1, out_buffer_size, out_file_pcm);
                current_count++;
                NSLog(@"当前音频解码%d帧",current_count);
            }
        }
    }
    //释放资源
    fclose(out_file_pcm);
    av_packet_free(&avpacket);
    swr_free(&swr_context);
    av_free(out_buffer);
    av_frame_free(&avframe);
    avcodec_close(avcodec_context);
    avformat_close_input(&avformat_context);
}

//视频编码
+ (void)ffmpegVideoEncodeOpenFile:(NSString *)filePath outFilePath:(NSString *)outFilePath {
    //1注册组件
    av_register_all();
    
    //2.初始化封装格式上下文 -> 处理为视频压缩数据格式
    AVFormatContext *avformat_context = avformat_alloc_context();
    //ffmepg程序推测输出文件类型->视频压缩数据格式
    const char *coutFilePath = [outFilePath UTF8String];
    //得到视频压缩数据格式(h264、h265、mpeg2等)
    AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
    //指定类型
    avformat_context->oformat = avoutput_format;
    
    //3.打开输入文件
    /*
     1.输出流  2.输出文件  3.权限->输出到文件中
     */
    if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
        NSLog(@"打开输出文件失败");
        return;
    }
    
    //4.创建输出码流 ->创建了一块内存空间 不知道类型 -> 需要视频流
    AVStream *av_video_stream = avformat_new_stream(avformat_context, NULL);//2.编码器id
    
    //5.查找视频编码器
    //1获取编码器上下文
    AVCodecContext *avcodec_context = av_video_stream->codec;
    //2设置编码器上下文参数 必须设置
    //目标：设置为一个视频编码器上下文->指定的是视频编码器
    //上下文种类：视频解码类型、编码类型、音频解码器、编码器等类型
    
    //设置编码器id
    avcodec_context->codec_id = avoutput_format->video_codec;
    //设置编码器类型->视频编码器
    avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
    //设置读取的像素数据格式 ->编码的是像素数据格式 yuv420p yuv422p
    //类型根据解码时指定的像素数据格式类型
    avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    //设置视频宽高
    avcodec_context->width = 640;
    avcodec_context->height = 352;
    //设置帧率 帧率越大视频约流畅 视频卡掉帧了 每秒25帧25.000 fps f帧数 ps时间
    avcodec_context->time_base.num = 1;
    avcodec_context->time_base.den = 25;
    
    
    
    
    
}

@end
